
#import <appkit/appkit.h>
#import <misckit/misckit.h>
#import <misckit/MiscDateView.h>

#import "Alexandra.h"
#import "Composer.h"
#import "NNTP.h"
#import "descriptors.h"
#import "ArticleViewControl.h"
#import "ArticleSet.h"
#import "NntpPanelControl.h"
#import "plain-subject.h"
#include <ctype.h>
#include <dbkit/DBTableView.h>

#define	OK_POSTED	240

static MiscList	*myComposers;
static XTDispatchAction *emacs_action;
static XTDispatchAction *none_action;

@implementation Composer

//-----------------------------------------------------------
// Klassen Methoden, Verwalten der Composer Liste 
//-----------------------------------------------------------

+ initialize
{
	char buf[MAXPATHLEN + 1];
	const char *userBindings;
	
	if(self==[Composer class])
	{
		myComposers=[[MiscList alloc] init];
		emacs_action=[[XTDispatchAction alloc] init];
		none_action=[[XTDispatchAction alloc] init];
		
		if([[NXBundle mainBundle] getPath:buf forResource:"emacs" ofType:"keys"])
			[emacs_action loadFromFile:buf estream:nil];
		
		if(userBindings=[NXApp defaultValue:"KeyString"]){
			[emacs_action addBindings:userBindings estream:nil];
			[none_action addBindings:userBindings estream:nil];
		}
	}
	return self;
}


+ newWindowForServer:(NNTP *)server;
	{
	Composer	*new;
	
	new=[[self alloc] initForServer:server];
	[myComposers addObject:new];
	return new;
	}	
	

+ removeComposer:(Composer *)composer;
	{
	[myComposers removeObject:composer];
	return self;
	}
	

+ killComposerForServer:(NNTP *)server;
	{
	Composer *c;
	
	for(c=[myComposers setLastObject];c;c=[myComposers setPreviousObject])
		if([c nntpServer]==server)
			[c free];
	return self;
	}

+ confirmTermination
{
	int r1,r2;
   int j=[myComposers count];
	if(j>0)
		do{
			r1=NXRunAlertPanel("ALEXANDRA","There are unsend messages.","Review unsend","Quit anyway","Cancel");
			if(r1==NX_ALERTOTHER)
				return nil;
			else if(r1==NX_ALERTALTERNATE){
				int i;
				for(i=j-1;i>=0;i--)
					[[myComposers objectAt:i] free];
				
				return self;
			}
			else{
				int i;
				BOOL canceled=FALSE;
				for(i=j-1;i>=0;i--){
					id aComposer=[myComposers objectAt:i];
					[[aComposer window] makeKeyAndOrderFront:self];
					r2=NXRunAlertPanel("ALEXANDRA","Send this message?","Send","Don't send","Cancel");
					if(r2==NX_ALERTOTHER){
						canceled=TRUE;
						break;
					}
					else if(r2==NX_ALERTDEFAULT)
						[aComposer post:self];
					else
						[aComposer free];
				}
				if(canceled)
					j=[myComposers count];
				else
					return self;
			}
		} while(j>0);
	
	return self;
}			
//-----------------------------------------------------------
// INIT & FREE 
//-----------------------------------------------------------

- initForServer:(NNTP *)theServer;
	{
	float char78Width;
	NXRect textFrame;
	NXRect windowFrame;
	NXAtom	ids[]={[KVPair keyIdentifier],[KVPair valueIdentifier],NULL};
	
	if([NXApp loadNibSection:"Composer.nib" owner:self withNames:NO]==nil)
		EM_ERROR(EGENFileNotFound,"Composer.nib",NULL);
	[self setNntpServer:theServer];
	headerController=[[[MiscTableController alloc] init] setTableView:headerTable withIdentifiers:ids];
	[self getDefaults];

	char78Width=78*[[theText font] getWidthOf:"A"];
	[theText getFrame:&textFrame];
	[[theText window] getFrame:&windowFrame];
	char78Width+=NX_WIDTH(&windowFrame)-NX_WIDTH(&textFrame);

	[[theText window] sizeWindow:char78Width :NX_HEIGHT(&windowFrame)];

   if([NXApp defaultBoolValue:DEFAULT_APPEND_SIG]){
      NXStream *signatureFile;
      const char *home=NXHomeDirectory();
      char *filename;

      filename=(char *)NXZoneCalloc(MYZONE,(strlen(home)+12),sizeof(char));
	  sprintf(filename,"%s/.signature",home);

      if((signatureFile=NXMapFile(filename,NX_READONLY))!=NULL){
  		[theText readText:signatureFile];
		[theText setSel:0:0];
      	[theText replaceSel:"\n-- \n"];
  		[theText setSel:0:0];
    	NXClose(signatureFile);
      }
		else
			NXRunAlertPanel("Alexandra","Cannot append signature."
					" File `~/.signature' not found.","Cancel",NULL,NULL);
      NXZoneFree(MYZONE,filename);
   }


	[[theText window] makeKeyAndOrderFront:self];
	
	return self;
	}



- getDefaults
	{
	KVPair		*emptypair=[[KVPair allocFromZone:MYZONE] init];
	char		defname[200];
	const char	*d;
	int			i,n=[NXApp defaultIntValue:DEFAULT_XHEADER_COUNT];

	[headerController empty:nil];
	for(i=0;i<n;i++)
		{
		sprintf(defname,DEFAULT_XHEADERS,i+1);
		d=[NXApp defaultValue:defname];
		if(d)
			[headerController addRow:[[KVPair alloc] init:d delimiter:':']];
		else
			[headerController addRow:emptypair];
		}
	[emptypair free];
   	return self;
	}
  
  
- free
{
   [[self class] removeComposer:self];
   [[[headerTable window] close] free];
   [[window close] free];

   if(subject!=NULL)
      free(subject);
   if(newsgroups!=NULL)
      free(newsgroups);
   if(references!=NULL)
      free(references);
   if(theQuotingStream!=NULL)
      NXCloseMemory(theQuotingStream,NX_FREEBUFFER);
   return [super free];
}


- awakeFromNib
	{
	
	if(strcmp([NXApp defaultValue:DEFAULT_KEY_BASE],"emacs")==0)
		local_action=emacs_action;
	else
		local_action=none_action;
		
	theText=[theText docView];
	[theText setMonoFont:YES];
	[theText setFont:	[Font userFixedPitchFontOfSize:0 
							matrix:NX_FLIPPEDMATRIX]];
		
	[theText setInitialAction:local_action];
	[headerForm setNextText:theText];
	return self;
	}


//-----------------------------------------------------------
// quoting 
//-----------------------------------------------------------

- followup:sender
{
   char *newSubject,*oldSubject;
   BOOL wasReply;

   if(subject==NULL)
      return self;
   oldSubject=plain_subject(subject,&wasReply);
   newSubject=(char *)NXZoneCalloc(MYZONE,(strlen(subject)+5),sizeof(char));
   sprintf(newSubject,"Re: %s",oldSubject);
   [subjectCell setStringValueNoCopy:newSubject shouldFree:YES];

   [newsgroupCell setStringValue:newsgroups];
   [followupButton setIcon:[followupButton altIcon]];
   [followupButton setTitle:[followupButton altTitle]];
   [followupButton setAction:@selector(includeArticle:)];
   if(strlen(references)>0)
      insert_refs=TRUE;
   return self;
}

- includeArticle:sender
{
	char *buf;
	int len,maxlen;
	
    if(theQuotingStream!=NULL){
	 	 NXGetMemoryBuffer(theQuotingStream,&buf,&len,&maxlen);
       [theText setSel:0:0];
       [theText replaceSel:buf length:len];
    }
    [followupButton setEnabled:FALSE];
    return self;
}

- post:sender
{
   char month_name[12][4]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep",
                        "Oct","Nov","Dec"};
   char mime_header[]="MIME-Version: 1.0\nContent-Type: text/plain; charset=iso-8859-1\nContent-Transfer-Encoding: 8bit\n";
   char *a2String;
   NXStream *theStream;
   long bodyPos;
   int textLength;
   int line;
   int pos;
   NXBreakArray	*theBreaks=0;
   int nbreaks;
   NXLineDesc *breaks;
   BOOL asciiBody;
   BOOL userDefdFrom=NO;
   int statusCode;
   MiscList *headers;
   KVPair	*pair;

   if([newsgroupCell stringValue][0]=='\0'){
      NXRunAlertPanel("ALEXANDRA","No Newsgroups header field body. Can't post.",NULL,NULL,NULL);
      return nil;
   }
   if([subjectCell stringValue][0]=='\0'){
      NXRunAlertPanel("ALEXANDRA","No Subject header field body. Can't post.",NULL,NULL,NULL);
      return nil;
   }
   if([theText textLength]==0){
      NXRunAlertPanel("ALEXANDRA","No article body. Can't post.",NULL,NULL,NULL);
      return nil;
   }
   if([expireButton state] && ([dateView isDateValid:self]==FALSE))
      return nil;
	
	[headerTable endEditing];
	
   theStream=NXOpenMemory(NULL,0,NX_READWRITE);
   NXSeek(theStream,0,NX_FROMSTART);

   //Write MIME-Header
   asciiBody=[self asciiBody];
   if(asciiBody==FALSE)
      NXPrintf(theStream,"%s",mime_header);
   
   // Write Newsgroups: header
   NXPrintf(theStream,"Newsgroups: %s\n",[newsgroupCell stringValue]);
   
   // Write Subject: header
   NXPrintf(theStream,"Subject: %s\n",[subjectCell stringValue]);

   // Write References: header
   if(insert_refs==TRUE)
      NXPrintf(theStream,"References: %s\n",references);

	headers=[headerController rows];
	for(pair=[headers setFirstObject]; pair; pair=[headers setNextObject])
		if([pair hasValue])
			{
			if(!strcasecmp([pair key],"from"))
				userDefdFrom=YES;
			NXPrintf(theStream,"%s: %s\n",[pair key],[pair value]);
			}

   // Write Expires: header
   if([expireButton state]){
      int day=[dateView getDay:self];
      const char *month=month_name[[dateView getMonth:self]-1];
      int year=[dateView getYear:self];

      NXPrintf(theStream,"Expires: %d %s %d 00:00:00 GMT\n",day,month,year);
   }

   if(!userDefdFrom){
	    a2String=[[FromHeaderController new] validFromAddress];
		if(!a2String)
			{
	      	NXCloseMemory(theStream,NX_FREEBUFFER);
			return nil;
			}
	    NXPrintf(theStream,"From: %s\n",a2String);
		free(a2String);
   }
   NXPrintf(theStream,"X-Newsreader: %s.app (%s)\n",
   						[NXApp appName],[NXApp appVersion]);

   //Write end of header
   NXPutc(theStream,'\n');
   bodyPos=NXTell(theStream);
   
   /*write body*/
   textLength=[theText textLength];
   object_getInstanceVariable(theText, "theBreaks", (void **)&theBreaks);
   nbreaks = theBreaks->chunk.used/sizeof(NXLineDesc);
   breaks = theBreaks->breaks;

   for(pos=0,line=0;line<nbreaks && pos<textLength;line++) {
      int lineChange;
      int endParagraph;
      int len,nlen;
      char *buf,*realbuf;

      lineChange = breaks[line] & 0x8000;
      endParagraph = breaks[line] & 0x4000;
		
      len = breaks[line] & 0x3fff;
      buf = malloc(len+2);
      realbuf=buf;
      buf++;
      nlen = [theText getSubstring:buf start:pos length:len];
      
      if(buf[0]=='.'){
         buf--;
         nlen++;
         buf[0]='.';
      }

      if((nlen>0)&&(buf[nlen-1]!='\n')){
         if(buf[nlen-1]=='\0')
            buf[nlen-1]='\n';
         else{
            buf[nlen] = '\n';
            nlen++;
         }
      }

      NXWrite(theStream,buf,nlen);	
      free(realbuf);
      pos+=len;
      if (lineChange!=0) {
         /* "if the line change bit is set, the descriptor is
          * the first field of a NXHeightChange...." */
         line += sizeof(NXHeightInfo) / sizeof(NXLineDesc);
      }
   }

   statusCode=[nntpServer postArticle:theStream];
   if(statusCode==OK_POSTED){
      [window close];
      NXCloseMemory(theStream,NX_FREEBUFFER);
      [self free];
		return self;
   }
	
   return nil;
}

- preparePostTo:(List *)newsgroupList selArticle:(Article *)anArticle 
	inView:(ArticleViewControl *)controlView;
{
   int i,j,strlength;
   char *newsgroup_str;
   char *ptr;
   const char *newsgroup;
   char *msg_id,*refs;

   //Make newsgrouplist string and set Newsgroups: field
   strlength=0;
   j=[newsgroupList count];
   for(i=0;i<j;i++)
      strlength += strlen([[newsgroupList objectAt:i] stringValue]);
   newsgroup_str=(char *)malloc((strlength+j)*sizeof(char));
   ptr=newsgroup_str;
   for(i=0;i<j;i++){
      newsgroup=[[newsgroupList objectAt:i] stringValue];
      strcpy(ptr,newsgroup);
      ptr+=strlen(newsgroup);
      if(i<j-1)
         *ptr=',';
      ptr++;
   }
   [newsgroupCell setStringValueNoCopy:newsgroup_str shouldFree:YES];

   //remember selected article fields
   subject=NULL;
   if(anArticle==nil)
      [followupButton setEnabled:FALSE];
   else{
      subject=NXCopyStringBuffer([anArticle header]->fieldBody[SUBJECT]);
      
      //where to followup?
      if([anArticle header]->fieldBody[FOLLOWUP_TO]!=NULL)
         newsgroups=NXCopyStringBuffer([anArticle header]->fieldBody[FOLLOWUP_TO]);
      else
         if([anArticle header]->fieldBody[NEWSGROUPS]!=NULL)
            newsgroups=NXCopyStringBuffer([anArticle header]->fieldBody[NEWSGROUPS]);
      
      //References field
      i=0; j=0;
      refs=[anArticle header]->fieldBody[REFS];
      msg_id=[anArticle header]->fieldBody[MSG_ID];
      if(refs!=NULL)
         i=strlen(refs);
      if(msg_id!=NULL)
         j=strlen(msg_id);
      references=(char *)malloc((i+j+3)*sizeof(char));
      references[0]='\0';
      if(refs!=NULL)
         strcpy(references,refs);
      if(msg_id!=NULL){
         if(strlen(references)>0)
            strcat(references," ");
         strcat(references,msg_id);
      }
      insert_refs=FALSE;

      // Copy article body 
      theQuotingStream=NXOpenMemory(NULL,0,NX_READWRITE);
      if([anArticle header]->fieldBody[FROM]!=NULL)
         NXPrintf(theQuotingStream,"%s wrote:\n",[anArticle header]->fieldBody[FROM]);
      [controlView writeQuotedText:theQuotingStream];
   }
   //set DateViews date as today
   [dateView getTodaysDate:self];

   return self;
}

- window
{
   return window;
}

- setNntpServer:(NNTP *)nntp
{
   nntpServer=nntp;
   return self;
}


- (NNTP *)nntpServer;
{
   return nntpServer;
}


- printText:sender
{
   if([theText textLength]!=0){
		[[NXApp printInfo] setHorizPagination:NX_FITPAGINATION];
      [theText printPSCode:self];
	}
	
   return self;
}


- (BOOL)asciiBody
	{
	NXStream	*textStream;
	int			len,maxlen;
	char		*p;
	
	textStream= NXOpenMemory(NULL,0,NX_WRITEONLY);
	[theText writeText:textStream];
	NXGetMemoryBuffer(textStream,&p,&len,&maxlen);
	for(;len>0;p++,len--)
		if(!NXIsAscii(*p))
			break;
	NXCloseMemory(textStream,NX_FREEBUFFER);
	return (len<=0);
	}
	

- theText
{
   return theText;
}


- windowWillClose:sender
{
   if([theText textLength]!=0){
      int answer=NXRunAlertPanel("ALEXANDRA","Message was not posted.","Close Anyway", "Cancel",NULL);
   if(answer==NX_ALERTALTERNATE)
      return nil;
   }
   return [self free];
}


- windowWillMiniaturize:sender toMiniwindow:miniwindow
{
   [sender setMiniwindowIcon:"respond"];
   return self;
}


- windowWillReturnFieldEditor:sender toObject:client
{
	return [XText newFieldEditorFor:sender
				initialAction:local_action
				estream:nil];
}

- rot13:sender
{
	char *buf;
	int i,j=[theText textLength];
	
	buf=malloc((j+2)*sizeof(char));
	[theText getSubstring:buf start:0 length:j];
	buf[j]='\0';
	
	for(i=0;i<j;i++) {
   	if(islower(buf[i]))
			buf[i]=(((buf[i]-'a')+13)%26)+'a';
      else if(isupper(buf[i]))
		  	buf[i]=(((buf[i]-'A')+13)%26)+'A';
   }

	[theText setText:buf];
	
	free(buf);
	
	return self;
}





@end
